package pong.game.objects;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

import pong.game.Data;
import pong.mathematic.Vector2D;
import pong.mathematic.Vector3D;
import android.content.Context;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;

public abstract class GL_Object {

	private static final int EMPTY = -1;

	private static final String TAG = "GL_Object";

	/** Size of the position data in elements. */
	private static final int VERTEX_DATA_SIZE = 3;
	/** Size of the color data in elements. */
	private static final int COLOR_DATA_SIZE = 4;
	/** Size of the normal data in elements. */
	private static final int NORMAL_DATA_SIZE = 3;
	/** Size of the texture coordinate data in elements. */
	private static final int TEXTURE_COORDINATE_DATA_SIZE = 2;

	private static float[] vertexes = new float[12];
	private static FloatBuffer mVertexes = null;
	private static FloatBuffer mTextureCoordinates = null;

	public float height = 0;
	public float width = 0;

	protected int[] textures = { 0 };

	protected boolean needLoadNewTexture = false;
	protected Vector3D center = new Vector3D();

	protected float scaleValue = 1f;
	protected float rotationAngle = 0f;

	protected FloatBuffer mNormals;
	protected FloatBuffer mColors;

	private float curAlpha = 1f;

	protected boolean isAlphaAnimating = false;
	protected boolean isScaleAnimating = false;
	protected boolean isRotateAnimating = false;
	protected boolean isWhite = false;

	private long startAlphaTime = EMPTY;
	private long startScaleTime = EMPTY;
	private long startRotateTime = EMPTY;

	private long animationAlphaTime = EMPTY;
	private long animationScaleTime = EMPTY;
	private long animationRotateTime = EMPTY;
	private float startAlphaValue;
	private float finishAlphaValue;

	private float startRotateValue;
	private float finishRotateValue;

	private float startScaleValue;
	private float finishScaleValue;

	public GL_Object() {
		this(new Vector3D(0, 0, 0), 0.5f, 0.5f);
	}

	public GL_Object(Vector3D center, float width, float height) {// , int
																	// visibleTime,
																	// float
																	// maxCurAlpha)
																	// {
		this.width = width;
		this.height = height;
		this.center = center;
		this.startAlphaTime = System.currentTimeMillis();
		this.startScaleTime = this.startAlphaTime;
		// this.visibleTime = startTime + visibleTime;
		// this.beginShow(maxCurAlpha, visibleTime);

		init3D();
	}

	private void init3D() {
		initVertexes();
		initTextureCoordinates();
		initNormals();
		initColors(getAlpha());
	}

	private void initVertexes() {

		if (mVertexes != null)
			return;

		vertexes[0] = -0.5f;
		vertexes[1] = 0.5f;
		vertexes[2] = center.getZ();

		vertexes[3] = -0.5f;
		vertexes[4] = -0.5f;
		vertexes[5] = center.getZ();

		vertexes[6] = 0.5f;
		vertexes[7] = 0.5f;
		vertexes[8] = center.getZ();

		vertexes[9] = 0.5f;
		vertexes[10] = -0.5f;
		vertexes[11] = center.getZ();

		ByteBuffer bb = ByteBuffer.allocateDirect(vertexes.length * 4);
		bb.order(ByteOrder.nativeOrder());
		mVertexes = bb.asFloatBuffer();
		mVertexes.put(vertexes);
		mVertexes.position(0);
	}

	private void initTextureCoordinates() {

		if (mTextureCoordinates != null)
			return;

		float[] texture = { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f };

		ByteBuffer bb = ByteBuffer.allocateDirect(texture.length * 4);
		bb.order(ByteOrder.nativeOrder());
		mTextureCoordinates = bb.asFloatBuffer();
		mTextureCoordinates.put(texture);
		mTextureCoordinates.position(0);
	}

	private void initNormals() {

		float[] normals = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
				0.0f, 0.0f, 1.0f };

		ByteBuffer bb = ByteBuffer.allocateDirect(normals.length * 4);
		bb.order(ByteOrder.nativeOrder());
		mNormals = bb.asFloatBuffer();
		mNormals.put(normals);
		mNormals.position(0);
	}

	private void initColors(float alpha) {
		// R, G, B, A
		float[] colors = { 1.0f, 1.0f, 1.0f, alpha, 1.0f, 1.0f, 1.0f, alpha, 1.0f, 1.0f, 1.0f, alpha, 1.0f, 1.0f, 1.0f,
				alpha, 1.0f, 1.0f, 1.0f, alpha, 1.0f, 1.0f, 1.0f, alpha };

		ByteBuffer bb = ByteBuffer.allocateDirect(colors.length * 4);
		bb.order(ByteOrder.nativeOrder());
		mColors = bb.asFloatBuffer();
		mColors.put(colors);
		mColors.position(0);
	}

	public void render(GL10 gl, float[] mModelMatrix, float[] mMVPMatrix, float[] mViewMatrix,
			float[] mProjectionMatrix, int mProgramHandle, float[] mLightPosInEyeSpace) {

		if (isAlphaAnimating) {
			updateAlphaFromTime();
			initColors(getAlpha());
		}
		if (isScaleAnimating) {
			updateScaleFromTime();
		}
		if (isRotateAnimating)
			updateRotateFromTime();

		int mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVPMatrix");
		int mMVMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVMatrix");
		int mLightPosHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_LightPos");
		int mTextureUniformHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_Texture");
		int mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
		int mColorHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Color");
		int mNormalHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Normal");
		int mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate");

		// GLES20.glBlendFunc(GLES20.GL_CONSTANT_COLOR, GLES20.GL_ONE); //
		// Perfect light

		// gl.glColor4f(1f, 1f, 1f, getAlpha());

		// GLES20.glBlendColor(1f, 0f, 0.5f, getAlpha());

		Matrix.setIdentityM(mModelMatrix, 0);

		Matrix.translateM(mModelMatrix, 0, getCenter().getX(), getCenter().getY(), getCenter().getZ());
		Matrix.rotateM(mModelMatrix, 0, rotationAngle, 0.0f, 0.0f, 1.0f);
		Matrix.scaleM(mModelMatrix, 0, getScaleValue() * width, getScaleValue() * height, getScaleValue());

		mVertexes.position(0);
		GLES20.glVertexAttribPointer(mPositionHandle, VERTEX_DATA_SIZE, GLES20.GL_FLOAT, false, 0, mVertexes);
		GLES20.glEnableVertexAttribArray(mPositionHandle);

		// Pass in the color information
		mColors.position(0);
		GLES20.glVertexAttribPointer(mColorHandle, COLOR_DATA_SIZE, GLES20.GL_FLOAT, false, 0, mColors);
		GLES20.glEnableVertexAttribArray(mColorHandle);

		// Pass in the normal information
		mNormals.position(0);
		GLES20.glVertexAttribPointer(mNormalHandle, NORMAL_DATA_SIZE, GLES20.GL_FLOAT, false, 0, mNormals);
		GLES20.glEnableVertexAttribArray(mNormalHandle);

		// Pass in the texture coordinate information
		mTextureCoordinates.position(0);
		GLES20.glVertexAttribPointer(mTextureCoordinateHandle, TEXTURE_COORDINATE_DATA_SIZE, GLES20.GL_FLOAT, false, 0,
				mTextureCoordinates);
		GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);

		// //
		Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
		// Pass in the modelview matrix.
		GLES20.glUniformMatrix4fv(mMVMatrixHandle, 1, false, mMVPMatrix, 0);

		// This multiplies the modelview matrix by the projection matrix, and
		// stores the result in the MVP matrix
		// (which now contains model * view * projection).
		Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
		// Pass in the combined matrix.
		GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
		// Pass in the light position in eye space.
		GLES20.glUniform3f(mLightPosHandle, mLightPosInEyeSpace[0], mLightPosInEyeSpace[1], mLightPosInEyeSpace[2]);
		// Draw the cube.
		GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
	}

	/**
	 * @return true if item has an texture in video RAM
	 */
	public boolean hasTexture() {
		return textures[0] != 0;
	}

	public abstract boolean isIn(Vector2D point);

	protected void updateAlphaFromTime() {
		int curDuration = (int) (System.currentTimeMillis() - startAlphaTime);
		if (curDuration < 0) {
			curDuration = 0;
		}
		float alpha;
		if (curDuration >= animationAlphaTime) {
			alpha = finishAlphaValue;
			isAlphaAnimating = false;
		} else {
			alpha = startAlphaValue + (finishAlphaValue - startAlphaValue) * curDuration / animationAlphaTime;
		}
		this.setAlpha(alpha);
	}

	public void startAlphaAnimation(float startAlpha, float finishAlpha, int duration) {
		startAlphaAnimationDelayed(startAlpha, finishAlpha, duration, 0);
	}

	public void startAlphaAnimationDelayed(float startAlpha, float finishAlpha, int duration, int delay) {
		isAlphaAnimating = true;
		startAlphaTime = System.currentTimeMillis() + delay;
		startAlphaValue = startAlpha;
		finishAlphaValue = finishAlpha;
		animationAlphaTime = duration;
	}

	public void startRotateAnimation(float startRotate, float finishRotate, int duration) {
		startRotateAnimationDelayed(startRotate, finishRotate, duration, 0);
	}

	public void startRotateAnimationDelayed(float startRotate, float finishRotate, int duration, int delay) {
		isRotateAnimating = true;
		startRotateTime = System.currentTimeMillis() + delay;
		startRotateValue = startRotate;
		finishRotateValue = finishRotate;
		animationRotateTime = duration;
	}

	// ////////////////////////////

	protected void updateScaleFromTime() {
		int curDuration = (int) (System.currentTimeMillis() - startScaleTime);
		if (curDuration < 0) {
			curDuration = 0;
		}
		float scale;
		if (curDuration >= animationScaleTime) {
			scale = finishScaleValue;
			isScaleAnimating = false;
		} else {
			scale = startScaleValue + (finishScaleValue - startScaleValue) * curDuration / animationScaleTime;
		}
		this.setScaleValue(scale);
	}

	protected void updateRotateFromTime() {
		int curDuration = (int) (System.currentTimeMillis() - startRotateTime);
		if (curDuration < 0) {
			curDuration = 0;
		}
		float rotate;
		if (curDuration >= animationRotateTime) {
			rotate = finishRotateValue;
			isRotateAnimating = false;
		} else {
			rotate = startRotateValue + (finishRotateValue - startRotateValue) * curDuration / animationRotateTime;
		}
		this.setRotateionAngle(rotate);
	}

	public void startScaleAnimation(float startScale, float finishScale, int duration) {
		startScaleAnimationDelayed(startScale, finishScale, duration, 0);
	}

	public void startScaleAnimationDelayed(float startScale, float finishScale, int duration, int delay) {
		isScaleAnimating = true;
		startScaleTime = System.currentTimeMillis() + delay;
		startScaleValue = startScale;
		finishScaleValue = finishScale;
		animationScaleTime = duration;
	}

	public boolean isNewTexturePrepared() {
		return needLoadNewTexture;
	}

	public void setCenter(Vector3D new_center) {
		setCenter(new_center.getX(), new_center.getY(), new_center.getZ());
	}

	public void setCenter(Vector2D new_center, float z) {
		setCenter(new_center.getX(), new_center.getY(), z);
	}

	public void setCenter(float x, float y, float z) {
		center.setXYZ(x, y, z);
	}

	public void setTexture(int openGLId) {
		textures[0] = openGLId;
	}

	public void setTexture(int resourceId, Context context, boolean fastTexture) {
		setTexture(Data.getInstance().getTextureMemoryController()
				.createOpenGLTexture(context, resourceId, fastTexture));
	}

	public void resetTextureID() {
		textures[0] = 0;
	}

	public int getTextureID() {
		return textures[0];
	}

	public void setRotationAngle(float rotationAngle) {
		this.rotationAngle = rotationAngle;
	}

	public void setScaleValue(float scaleValue) {
		this.scaleValue = scaleValue;
	}

	public void setRotateionAngle(float rotateValue) {
		this.rotationAngle = rotateValue;
	}

	public float getRotationAngle() {
		return rotationAngle;
	}

	public float getScaleValue() {
		return scaleValue;
	}

	// protected void setImageBitmap(Bitmap bitmap) {
	// // GL_Helper.recycleBitmap(imageBitmap);
	// this.imageBitmap = bitmap;
	// }
	//
	// public Bitmap getImageBitmap() {
	// return this.imageBitmap;
	// }

	public Vector3D getCenter() {
		return center;
	}

	public float getAlpha() {
		return curAlpha;
	}

	public long getStartAlphaTime() {
		return startAlphaTime;
	}

	public void setAlpha(float curAlpha) {
		this.curAlpha = curAlpha;
		if (curAlpha < 0) {
			Log.e(TAG, "The Alpha Value must be bigger than 0");
		}
	}

	public boolean isScaleAnimation() {
		return this.isScaleAnimating;
	}

	public boolean isAlphaAnimation() {
		return this.isAlphaAnimating;
	}

	public boolean isRotateAnimating() {
		return this.isRotateAnimating;
	}

	public boolean isWhite() {
		return isWhite;
	}

	public void setWhite(boolean value) {
		this.isWhite = value;
	}
}
